package com.mumu.muPictureBackend.manager;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.*;
import com.mumu.muPictureBackend.config.CosClientConfig;
import com.mumu.muPictureBackend.exception.BusinessException;
import com.mumu.muPictureBackend.exception.ErrorCode;
import com.mumu.muPictureBackend.exception.ThrowUtils;
import com.mumu.muPictureBackend.model.vo.UploadPictureResult;
import com.qcloud.cos.model.COSObject;
import com.qcloud.cos.model.COSObjectInputStream;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.model.ciModel.persistence.ImageInfo;
import com.qcloud.cos.utils.IOUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Service
@Slf4j
@Deprecated//已废弃的方法 已经改为upload包中的模板方法
public class FileManager {
    //cos阿里云的配置类(里面有key,桶名称等配置)
    @Resource
    private CosClientConfig cosClientConfig;
    //文件上传类
    @Resource
    private CosManager cosManager;

    /**
     *
     * @param multipartFile    图片包装类
     * @param uploadPathPrefix 上传的图片后缀名字
     * @return 返回该路径的基本信息 包括名字 大小 宽度 高度 宽高比 封装起来之后返回
     */
    public UploadPictureResult uploadRequest(MultipartFile multipartFile, String uploadPathPrefix) {
        //文件上传后需要返回文件信息 文件信息都封装到这个里面了
        UploadPictureResult uploadPictureResult = new UploadPictureResult();
        //1. 校验文件(文件大小,后缀名是否合格)
        ValidPicture(multipartFile);
        //2. 指定路径 文件名字就是 原始文件名+uuid+日期 即可 uuid是防止重复
        String uuid = RandomUtil.randomNumbers(16);
        String originalFilename = multipartFile.getOriginalFilename();
        String filePath = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid, originalFilename);
        String uploadPath = String.format("%s/%s", uploadPathPrefix, filePath);//根据文件名前缀来分类文件夹 统一管理 这个前缀可以是public:/userid 这样就根据每个用户来分类文件了
        //3. 上传图片到cos中 思路:先将该文件写入到本地磁盘 再从本地中读取该文件 然后将该文件上传到COS中 上传后就将该本地文件删除
        //将file定义到try外面 在try里面用 finally中销毁
        File file = null;
        try {
            //1. 先将文件存储到本地 null只是不指定后缀名 因为具体后缀path中已经指定
            file = File.createTempFile(uploadPath, null);
            //2. 将该文件写入到本地中
            multipartFile.transferTo(file);
            //3. 将该文件上传到cos上 返回值是文件的信息封装类 第一个参数是该文件的路径 第二个参数就是上传的文件内容
            PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);

            //4. 将图片上传到cos上 然后将图片信息封装到 uploadPictureResult 中 最后返回
            uploadPictureToCos(uploadPath, file, uploadPictureResult, originalFilename);
            //返回可访问的地址
            return uploadPictureResult;

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //将临时文件删除 该文件已经用完了 那么就删除
            if (file != null) {
                boolean delete = file.delete();
                if (!delete) {
                    log.info("删除失败 文件路径{}", file.getPath());
                }
            }
        }
    }

    private void uploadPictureToCos(String uploadPath, File file, UploadPictureResult uploadPictureResult, String originalFilename) {
        //3. 将该文件上传到cos上 返回值是文件的信息封装类 第一个参数是该文件的路径 第二个参数就是上传的文件内容
        PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);

        //4. 提取图片的信息 然后封装返回
        ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();

        int width = imageInfo.getWidth();
        int height = imageInfo.getHeight();
        double picScale = width * 1.0 / height;//宽高比;
//            url就是host+该桶中的该文件的地址  然后就可以直接通过这个url去下载该图片
        uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + uploadPath);
        uploadPictureResult.setName(FileUtil.mainName(originalFilename));//提取文件名 不包含后缀
        uploadPictureResult.setPicSize(FileUtil.size(file));
        uploadPictureResult.setPicWidth(width);
        uploadPictureResult.setPicHeight(height);
        uploadPictureResult.setPicScale(picScale);
        uploadPictureResult.setPicFormat(imageInfo.getFormat());
    }


    /**
     *
     * @param fileUrl          图片包装类
     * @param uploadPathPrefix 上传的图片后缀名字
     * @return 返回该路径的基本信息 包括名字 大小 宽度 高度 宽高比 封装起来之后返回
     */
    public UploadPictureResult uploadRequestByUrl(String fileUrl, String uploadPathPrefix) {
        //文件上传后需要返回文件信息 文件信息都封装到这个里面了
        UploadPictureResult uploadPictureResult = new UploadPictureResult();
        //1. 校验url地址
        validPictureUrl(fileUrl);
        //2. 指定路径 文件名字就是 原始文件名+uuid+日期 即可 uuid是防止重复
        String uuid = RandomUtil.randomNumbers(16);
//        提取url中的文件名
        String originalFilename = FileUtil.mainName(fileUrl);
        String filePath = String.format("%s_%s.%s", DateUtil.formatDate(new Date()), uuid, originalFilename);
        String uploadPath = String.format("%s/%s", uploadPathPrefix, filePath);//根据文件名前缀来分类文件夹 统一管理 这个前缀可以是public:/userid 这样就根据每个用户来分类文件了
        //3. 上传图片到cos中 思路:先将该文件写入到本地磁盘 再从本地中读取该文件 然后将该文件上传到COS中 上传后就将该本地文件删除
        //将file定义到try外面 在try里面用 finally中销毁
        File file = null;
        try {
            //1. 先将文件存储到本地(现在已经创建一个名为uploadPath的临时文件了 现在还没有数据 file指向着这个文件 然后直接将数据写入这个file里面就可以了 null只是不指定后缀名 因为具体后缀path中已经指定
            file = File.createTempFile(uploadPath, null);
            //2. 根据url地址将文件图片下载到本地file中
            HttpUtil.downloadFile(fileUrl, file);
            //3. 将图片上传到cos上 然后将图片信息封装到 uploadPictureResult 中 最后返回
            uploadPictureToCos(uploadPath, file, uploadPictureResult, originalFilename);
            //返回可访问的地址
            return uploadPictureResult;

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //将临时文件删除 该文件已经用完了 那么就删除
            if (file != null) {
                boolean delete = file.delete();
                if (!delete) {
                    log.info("删除失败 文件路径{}", file.getPath());
                }
            }
        }
    }


    /**
     * 校验文件 校验图片大小,后缀名
     *
     * @param multipartFile 图片文件
     */
    private void ValidPicture(MultipartFile multipartFile) {
        ThrowUtils.throwIf(multipartFile == null, ErrorCode.NOT_FOUND_ERROR, "图片呢??");
        //1. 校验文件大小
        long fileSize = multipartFile.getSize();//获取该文件的大小 单位:字节
        final long ONE_M = 1024 * 1024;
        ThrowUtils.throwIf(fileSize > 2 * ONE_M, ErrorCode.PARAMS_ERROR, "图片能再大点嘛?(图片太大啦 不要超过2M 硬盘有限 装不下)");
        //2. 校验文件后缀
        String suffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        //允许的图片后缀名列表:
        final List<String> ALLOW_FORMAT_LIST = Arrays.asList("jpeg", "png", "jpg", "webp");
        ThrowUtils.throwIf(!ALLOW_FORMAT_LIST.contains(suffix), ErrorCode.PARAMS_ERROR, "只支持:\"jpeg\", \"png\", \"jpg\", \"webp\" 这些图片格式哦");
    }


    /**
     * 从cos中获取图片 然后将图片转换成字节数组然后返回
     *
     * @param filePath cos上文件的路径
     * @return
     */
    public byte[] downloadPicture(String filePath) throws IOException {
        COSObjectInputStream cosObjectInputStream = null;
        try {
            //获取图片然后通过流转换成字节数组返回 为什么要用流? 流是边读边处理 读8KB发送给网络 再读8KB 然后再发 内存始终只占一小块 如果不用流 那么就是一整个文件占用内存 所以返回图片内容的时候是返回的该图片的数据流
            COSObject picture = cosManager.getObject(filePath);
            cosObjectInputStream = picture.getObjectContent();
            return IOUtils.toByteArray(cosObjectInputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //关流
            if (cosObjectInputStream != null) {
                cosObjectInputStream.close();
            }
        }
    }


    /**
     * 校验url地址是否合格
     *
     * @param fileUrl
     */
    private void validPictureUrl(String fileUrl) {
        //1. 判断是否非空
        ThrowUtils.throwIf(StrUtil.isBlank(fileUrl), ErrorCode.PARAMS_ERROR);
        //2. 判断格式是否合格 这里直接new一个URL如果报错了 就标识不是一个合格的url地址
        try {
            new URL(fileUrl);
        } catch (MalformedURLException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "url地址不合格");
        }
        //3. 判断是不是http/https开头的
        ThrowUtils.throwIf(!fileUrl.startsWith("http://") && !fileUrl.startsWith("https://"), ErrorCode.PARAMS_ERROR, "只支持http,https协议的url");
        //4. 使用HEAD请求去请求这个fileUrl 只返回响应头header 不返回实际内容(直接快速判断该url地址是否存在 不需要下载整个文件) 并且这个header中还包含该文件的类型,大小等信息可以快速判断 不过有些图片就是本身不带有head所以如果没有head就不校验类型和大小了
        HttpResponse httpResource = null;
        try {
            //获取HEAD请求:快速利用HEAD来判断该url是否存在
            httpResource = HttpUtil.createRequest(Method.HEAD, fileUrl)
                    .execute();
//            如果响应码不等于200 那么就说明不存在/该url内容存在但是没有响应头 那么就直接返回 不用进行下面的校验了
            if (httpResource.getStatus() != HttpStatus.HTTP_OK) {
                return;
            }
            // 因为有的URL是没有请求头的 所以没有请求头直接放行不校验 但是有请求头的话就简单校验一下类型还有大小即可 请求头中包含了文件大小类型
            //如果该文件有请求头 那么请求头中还包含该文件的类型,httpResource.header("Content-Type")就是获取文件类型 请求头里面还包含该文件的大小
            //请求头存在 那么就要校验文件类型,大小
            String contentType = httpResource.header("Content-Type");
//          校验文件类型
            if (StrUtil.isNotBlank(contentType)) {
                final List<String> ALLOW_CONTENT_TYPES = Arrays.asList("image/jpeg", "image/jgp", "image/png", "image/webp");//定义格式 都是image/开头的
                ThrowUtils.throwIf(!ALLOW_CONTENT_TYPES.contains(contentType), ErrorCode.PARAMS_ERROR, "只支持常规图片格式");
            }
//            校验文件大小
            String lengthStr = httpResource.header("Content-Length");
            if (StrUtil.isNotBlank(lengthStr)) {
                long length = Long.parseLong(lengthStr);
                final long ONE_M = 1024 * 1024;
                ThrowUtils.throwIf(length > 2 * ONE_M, ErrorCode.PARAMS_ERROR, "文件太大了 不要超过2M");
            }


        } finally {
//            释放资源
            if (httpResource != null) {
                httpResource.close();
            }
        }

    }


}
